package directory

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"mall/common/modelx"
	"mall/service/attachment/model"
	"mall/service/attachment/rpc/internal/svc"
	"mall/service/attachment/rpc/internal/utils/errorhandler"
	"mall/service/attachment/rpc/types/attachment"
	"mall/service/backend/common/errorx"
	"mall/service/backend/common/i18n"
	"mall/service/backend/common/utils/pointy"

	"github.com/qiniu/go-sdk/v7/auth/qbox"
	"github.com/qiniu/go-sdk/v7/storage"
	"github.com/sirupsen/logrus"
	"github.com/volatiletech/null/v8"
	"github.com/zeromicro/go-zero/core/logx"
)

type BackendGetRecordByIdLogic struct {
	ctx           context.Context
	svcCtx        *svc.ServiceContext
	bucketManager *storage.BucketManager
	bucket        string
	logx.Logger
}

func NewBackendGetRecordByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BackendGetRecordByIdLogic {
	return &BackendGetRecordByIdLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *BackendGetRecordByIdLogic) BackendGetRecordById(in *attachment.RecordIdReq) (*attachment.RecordInfo, error) {
	var diagnose uint32
	if in.Diagnose != nil {
		diagnose = *in.Diagnose
	}

	if diagnose == 0 {
		return l.backendGetRecordById(in)
	}

	mac := qbox.NewMac(l.svcCtx.Config.QiniuConf.AccessKey, l.svcCtx.Config.QiniuConf.SecretKey)

	cfg := storage.Config{
		UseHTTPS:      l.svcCtx.Config.QiniuConf.UseHTTPS, // 是否使用https域名进行资源管理
		UseCdnDomains: l.svcCtx.Config.QiniuConf.UseCdnDomains, // 上传是否使用CDN上传加速
	}
	// 指定空间所在的区域，如果不指定将自动探测
	// 如果没有特殊需求，默认不需要指定
	// cfg.Region=&storage.ZoneHuabei
	switch l.svcCtx.Config.QiniuConf.Region {
	case "z2":
		cfg.Region = &storage.ZoneHuanan
	}

	l.bucketManager = storage.NewBucketManager(mac, &cfg)
	l.bucket = l.svcCtx.Config.QiniuConf.Bucket

	var err error
	switch diagnose {
	case 1: // 获取文件信息
		err = l.qiniuStat(in)
	case 2: // 修改文件MimeType
		if in.Mime != nil && len(strings.TrimSpace(*in.Mime)) > 0 {
			err = l.qiniuChangeMime(in)
		}
	case 3: // 修改文件存储类型
		if in.Type != nil {
			err = l.qiniuChangeType(in)
		}
	case 4: // 修改文件MetaData
		if len(in.Meta) > 0 {
			err = l.qiniuChangeMeta(in)
		}
	}

	if err != nil {
		return nil, err
	}

	return &attachment.RecordInfo{}, nil
}

func (l *BackendGetRecordByIdLogic) backendGetRecordById(in *attachment.RecordIdReq) (*attachment.RecordInfo, error) {
	if in.Id == nil || len(*in.Id) == 0 {
		return nil, errorx.NewInvalidArgumentError(i18n.ValidationError)
	}

	var id uint64
	if i, err := strconv.ParseInt(*in.Id, 10, 64); err == nil {
		id = uint64(i)
	} else {
		return nil, errorx.NewInvalidArgumentError(i18n.ValidationError)
	}

	res, err := l.svcCtx.FileInfo.FindOne(l.ctx, nil, id)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	var updatedAt *string
	if res.PutTime > 0 {
		updatedAt = pointy.GetPointer(modelx.FormatTime(time.Unix(0, int64(res.PutTime)*100), ""))
	}
	var expiredAt *string
	if res.Expiration > 0 {
		expiredAt = pointy.GetPointer(modelx.FormatTime(time.Unix(int64(res.Expiration), 0), ""))
	}
	var meta *string
	if res.MetaValue.Valid {
		meta = pointy.GetPointer(res.MetaValue.String)
	}
	return &attachment.RecordInfo{
		Id:            pointy.GetPointer(strconv.FormatUint(res.ID, 10)),
		FileId:        &res.AttachmentID,
		ParentId:      &res.ParentID,
		Bucket:        &res.Bucket,
		Key:           &res.KeyValue,
		Size:          &res.Size,
		Hash:          &res.Hash,
		MimeType:      &res.MimeType,
		Type:          pointy.GetPointer(uint32(res.Type)),
		Status:        pointy.GetPointer(uint32(res.Status)),
		RestoreStatus: pointy.GetPointer(uint32(res.RestoreStatus)),
		Md5:           &res.MD5,
		EndUser:       &res.EndUser,
		MetaValue:     meta,
		DelStatus:     pointy.GetPointer(uint32(res.DelStatus)),
		UpdatedAt:     updatedAt,
		ExpiredAt:     expiredAt,
	}, nil
}

func (l *BackendGetRecordByIdLogic) parseAndFind(in *attachment.RecordIdReq) (string, *model.AttachmentFileInfo, error) {
	var key string
	var data *model.AttachmentFileInfo
	if in.Key != nil {
		key = strings.TrimSpace(*in.Key)

		if len(key) > 0 {
			filterMap := make(map[string]map[string][]any)
			filterMap[model.AttachmentFileInfoColumns.KeyValue] = map[string][]any{
				"equalTo": []any{key},
			}
			res, _, err := l.svcCtx.FileInfo.GetList(l.ctx, model.M{
				"pageSize": uint64(2),
			}, -1, filterMap)
			if err != nil {
				logrus.Info(fmt.Sprintf("Qiniu parseAndFind err: %+v", err))
				return "", nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}

			if len(res) > 1 {
				return "", nil, errorx.NewInternalError(i18n.NotSingularError)
			}

			if len(res) == 1 {
				data = res[0]
			} else {
				return key, nil, nil
			}
		}
	}

	if len(key) == 0 && in.Id != nil && len(strings.TrimSpace(*in.Id)) > 0 {
		if i, err := strconv.ParseInt(strings.TrimSpace(*in.Id), 10, 64); err == nil && i > 0 {
			res, err := l.svcCtx.FileInfo.FindOne(l.ctx, nil, uint64(i))
			if err != nil {
				return "", nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}

			key = res.KeyValue
			data = res
		}
	}

	var bucket string
	if data != nil {
		bucket = data.Bucket
	}
	if len(bucket) > 0 {
		l.bucket = bucket
	}

	return key, data, nil
}

func (l *BackendGetRecordByIdLogic) createOrUpdate(key string, data *model.AttachmentFileInfo) error {
	fileInfo, err := l.bucketManager.Stat(l.bucket, key)
	if err != nil {
		logrus.Info(fmt.Sprintf("Qiniu Stat err: %+v", err))
		return err
	}

	// logrus.Info(fmt.Sprintf("Qiniu Stat: %+v", fileInfo))

	// 可以解析文件的PutTime
	// logrus.Info(fmt.Sprintf("Qiniu Stat PutTime: %+v", storage.ParsePutTime(fileInfo.PutTime)))

	if data != nil {
		data.Size = uint64(fileInfo.Fsize)
		data.Hash = fileInfo.Hash
		data.MimeType = fileInfo.MimeType
		data.Type = uint8(fileInfo.Type)
		data.PutTime = uint64(fileInfo.PutTime)
		data.RestoreStatus = uint8(fileInfo.RestoreStatus)
		data.Status = uint8(fileInfo.Status)
		data.MD5 = fileInfo.Md5
		data.EndUser = fileInfo.EndUser
		data.Expiration = uint64(fileInfo.Expiration)
		data.TransitionToIa = uint64(fileInfo.TransitionToIA)
		data.TransitionToArchive = uint64(fileInfo.TransitionToArchive)
		data.TransitionToDeepArchive = uint64(fileInfo.TransitionToDeepArchive)
		if len(fileInfo.Parts) > 0 {
			var parts []string
			for _, partId := range fileInfo.Parts {
				parts = append(parts, strconv.FormatInt(partId, 10))
			}
			data.Parts = strings.Join(parts, ",")
		}
		if fileInfo.MetaData != nil {
			var items []string
			for mk, mv := range fileInfo.MetaData {
				items = append(items, mk + ":" + mv)
			}
			data.MetaValue = null.StringFrom(strings.Join(items, ";"))
		}

		err := l.svcCtx.FileInfo.Update(l.ctx, nil, data)
		if err != nil {
			return err
		}
	} else {
		var newData model.AttachmentFileInfo
		newData.KeyValue = key
		newData.Bucket = l.bucket
		newData.Size = uint64(fileInfo.Fsize)
		newData.Hash = fileInfo.Hash
		newData.MimeType = fileInfo.MimeType
		newData.Type = uint8(fileInfo.Type)
		newData.PutTime = uint64(fileInfo.PutTime)
		newData.RestoreStatus = uint8(fileInfo.RestoreStatus)
		newData.Status = uint8(fileInfo.Status)
		newData.MD5 = fileInfo.Md5
		newData.EndUser = fileInfo.EndUser
		newData.Expiration = uint64(fileInfo.Expiration)
		newData.TransitionToIa = uint64(fileInfo.TransitionToIA)
		newData.TransitionToArchive = uint64(fileInfo.TransitionToArchive)
		newData.TransitionToDeepArchive = uint64(fileInfo.TransitionToDeepArchive)
		if len(fileInfo.Parts) > 0 {
			var parts []string
			for _, partId := range fileInfo.Parts {
				parts = append(parts, strconv.FormatInt(partId, 10))
			}
			newData.Parts = strings.Join(parts, ",")
		}
		if fileInfo.MetaData != nil {
			var items []string
			for mk, mv := range fileInfo.MetaData {
				items = append(items, mk + ":" + mv)
			}
			newData.MetaValue = null.StringFrom(strings.Join(items, ";"))
		}

		err := l.svcCtx.FileInfo.Insert(l.ctx, nil, &newData)
		if err != nil {
			return err
		}
	}

	return nil
}

func (l *BackendGetRecordByIdLogic) qiniuStat(in *attachment.RecordIdReq) error {
	key, data, err := l.parseAndFind(in)
	if err != nil {
		return err
	}

	if len(key) > 0 {
		err = l.createOrUpdate(key, data)
		if err != nil {
			return errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}

func (l *BackendGetRecordByIdLogic) qiniuChangeMime(in *attachment.RecordIdReq) error {
	key, data, err := l.parseAndFind(in)
	if err != nil {
		return err
	}

	if len(key) > 0 {
		err := l.bucketManager.ChangeMime(l.bucket, key, strings.TrimSpace(*in.Mime))
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu ChangeMime err: %+v", err))
			return errorx.NewInternalError("file.updateFileFailed")
		}

		err = l.createOrUpdate(key, data)
		if err != nil {
			return errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}

func (l *BackendGetRecordByIdLogic) qiniuChangeMeta(in *attachment.RecordIdReq) error {
	key, data, err := l.parseAndFind(in)
	if err != nil {
		return err
	}

	if len(key) > 0 {
		metaMap := make(map[string]string)
		for _, m := range in.Meta {
			if m.Key != nil && len(*m.Key) > 0 && m.Value != nil && len(*m.Value) > 0 {
				metaMap[*m.Key] = *m.Value
			}
			// map 中为需要修改的 metas，只包含需要更改的 metas，可增加；
			// 服务接口中 key 必须包含 x-qn-meta- 前缀，SDK 会对 metas 参数中的 key 进行检测;
			//   - key 如果包含了 x-qn-meta- 前缀，则直接使用 key；
			//   - key 如果不包含了 x-qn-meta- 前缀，则内部会为 key 拼接 x-qn-meta- 前缀。
			// 如果 x-qn-meta-c 存在，则将值修改为 x-qn-meta-cc-value，如果不存在则增加 x-qn-meta-c 键值对
			// metaMap["x-qn-meta-c"] = "x-qn-meta-cc-value"
		}
		err := l.bucketManager.ChangeMeta(l.bucket, key, metaMap)
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu ChangeMeta err: %+v", err))
			return errorx.NewInternalError("file.updateFileFailed")
		}

		err = l.createOrUpdate(key, data)
		if err != nil {
			return errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}

func (l *BackendGetRecordByIdLogic) qiniuChangeType(in *attachment.RecordIdReq) error {
	key, data, err := l.parseAndFind(in)
	if err != nil {
		return err
	}

	if len(key) > 0 {
		err := l.bucketManager.ChangeType(l.bucket, key, int(*in.Type))
		if err != nil {
			logrus.Info(fmt.Sprintf("Qiniu ChangeType err: %+v", err))
			return errorx.NewInternalError("file.updateFileFailed")
		}

		err = l.createOrUpdate(key, data)
		if err != nil {
			return errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		return nil
	}

	return errorx.NewNotFoundError(i18n.TargetNotFound)
}
